Las ultimas elecciones presidenciales 2021 disputadas entre el partido fuerza popular y peru libre ha traido consigo una serie de cuestionamientos y margenes ajustados en cuanto a los votos que definiieron al nuevo presidente del Peru ,es asi que el partido Peru Libre ha conseguido una diferencia no muy marcada en proporcion a su adversario, al cual le lleva ventaja de +40K votos a favor.
es asi que el partido fuerza popular ha planteado un recurso de inpugnacion(anulacion) de votos en mesas de sufragio en contra de Peru LIbre sustentando indicios de fraude. entre los sustentos la cual analizaremos, plantean que
Dataset :se toma la data de votos por cada MESA de votacion de la pagina oficial de la ONPE con actas procesadas al 99,616% hasta la fecha 11 de junio
“Un outlier es una observación que se desvía tanto de otras observaciones que despierta la sospecha de haber sido generado por un mecanismo diferente” [Hawkings, 1980].
dataset csv con el total del universo de las mesas de sufragio obtenidas de la pagina oficial de la ONPE actualizado al 11-06-2021
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from scipy.stats import norm
dataframe=pd.read_csv("C:/Users/firebaseapk/Documents/data/elecciones_libro.csv", sep=",")
C:\Users\firebaseapk\anaconda3\lib\site-packages\IPython\core\interactiveshell.py:3165: DtypeWarning: Columns (25,26) have mixed types.Specify dtype option on import or set low_memory=False. has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
dataframe=dataframe.iloc[:, 0:19]
dataframe.head()
| mesa | CCODI_UBIGEO | TNOMB_LOCAL | REGION | REGION_NATURAL | DEPARTAMENTO | PROVINCIA | DISTRITO | NNUME_HABILM | PERU_LIBRE | FUERZA_POPULAR | VALIDOS | BLANCO | NULOS | IMPUGNADOS | EMITIDOS | NULO_VICIADO | MESAS_LOCAL | MESAS_DISTR | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 10101 | IE 18288 ISABEL LYNCH DE RUBIO | NORTE | Sierra | AMAZONAS | CHACHAPOYAS | CHACHAPOYAS | 300 | 118 | 81 | 199 | 1 | 14 | 0 | 214 | 15 | 15 | 73 |
| 1 | 2 | 10101 | IE 18288 ISABEL LYNCH DE RUBIO | NORTE | Sierra | AMAZONAS | CHACHAPOYAS | CHACHAPOYAS | 300 | 123 | 87 | 210 | 0 | 16 | 0 | 226 | 16 | 15 | 73 |
| 2 | 3 | 10101 | IE 18288 ISABEL LYNCH DE RUBIO | NORTE | Sierra | AMAZONAS | CHACHAPOYAS | CHACHAPOYAS | 300 | 122 | 73 | 195 | 2 | 19 | 0 | 216 | 21 | 15 | 73 |
| 3 | 4 | 10101 | IE 18288 ISABEL LYNCH DE RUBIO | NORTE | Sierra | AMAZONAS | CHACHAPOYAS | CHACHAPOYAS | 300 | 131 | 89 | 220 | 3 | 14 | 0 | 237 | 17 | 15 | 73 |
| 4 | 5 | 10101 | IE 18288 ISABEL LYNCH DE RUBIO | NORTE | Sierra | AMAZONAS | CHACHAPOYAS | CHACHAPOYAS | 300 | 107 | 95 | 202 | 1 | 19 | 0 | 222 | 20 | 15 | 73 |
seleccionamos las columnas de 0 a 19 , las cuales tienen informacion relevante para este analisis
dataframe.describe()
| mesa | CCODI_UBIGEO | NNUME_HABILM | PERU_LIBRE | FUERZA_POPULAR | VALIDOS | BLANCO | NULOS | IMPUGNADOS | EMITIDOS | NULO_VICIADO | MESAS_LOCAL | MESAS_DISTR | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.0 | 82730.000000 | 82730.000000 | 82730.000000 | 82730.000000 |
| mean | 72170.668367 | 127941.978327 | 292.481941 | 105.308751 | 103.338994 | 208.647746 | 1.442633 | 12.592675 | 0.0 | 222.683053 | 14.035308 | 12.419485 | 475.465538 |
| std | 165042.509969 | 56767.512731 | 26.273734 | 45.444055 | 51.254139 | 35.318387 | 1.647905 | 8.704317 | 0.0 | 36.424805 | 8.898503 | 10.492277 | 596.970533 |
| min | 1.000000 | 10101.000000 | 43.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 10.000000 | 0.000000 | 1.000000 | 1.000000 |
| 25% | 20705.250000 | 90507.000000 | 300.000000 | 73.000000 | 61.000000 | 194.000000 | 0.000000 | 8.000000 | 0.0 | 208.000000 | 9.000000 | 6.000000 | 51.000000 |
| 50% | 41469.500000 | 140108.000000 | 300.000000 | 98.000000 | 111.000000 | 216.000000 | 1.000000 | 11.000000 | 0.0 | 231.000000 | 13.000000 | 10.000000 | 234.000000 |
| 75% | 62310.750000 | 140801.000000 | 300.000000 | 130.000000 | 144.000000 | 232.000000 | 2.000000 | 15.000000 | 0.0 | 247.000000 | 17.000000 | 15.000000 | 627.000000 |
| max | 903104.000000 | 250401.000000 | 306.000000 | 269.000000 | 271.000000 | 284.000000 | 67.000000 | 300.000000 | 0.0 | 300.000000 | 300.000000 | 95.000000 | 2623.000000 |
PERU_LIBRE
FUERZA_POPULAR
dataframe.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 82730 entries, 0 to 82729 Data columns (total 19 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 mesa 82730 non-null int64 1 CCODI_UBIGEO 82730 non-null int64 2 TNOMB_LOCAL 82730 non-null object 3 REGION 82730 non-null object 4 REGION_NATURAL 82730 non-null object 5 DEPARTAMENTO 82730 non-null object 6 PROVINCIA 82730 non-null object 7 DISTRITO 82730 non-null object 8 NNUME_HABILM 82730 non-null int64 9 PERU_LIBRE 82730 non-null int64 10 FUERZA_POPULAR 82730 non-null int64 11 VALIDOS 82730 non-null int64 12 BLANCO 82730 non-null int64 13 NULOS 82730 non-null int64 14 IMPUGNADOS 82730 non-null int64 15 EMITIDOS 82730 non-null int64 16 NULO_VICIADO 82730 non-null int64 17 MESAS_LOCAL 82730 non-null int64 18 MESAS_DISTR 82730 non-null int64 dtypes: int64(13), object(6) memory usage: 12.0+ MB
dataframe.groupby("DEPARTAMENTO").size()
DEPARTAMENTO AMAZONAS 1104 ANCASH 3100 APURIMAC 1121 AREQUIPA 3950 AYACUCHO 1701 CAJAMARCA 3823 CALLAO 2747 CUSCO 3523 HUANCAVELICA 1091 HUANUCO 2047 ICA 2218 JUNIN 3421 LA LIBERTAD 4842 LAMBAYEQUE 3332 LIMA 27722 LORETO 2382 MADRE DE DIOS 395 MOQUEGUA 524 PASCO 695 PIURA 4790 PUNO 3163 SAN MARTIN 2182 TACNA 974 TUMBES 573 UCAYALI 1310 dtype: int64
agrupamos las mesas de votacion por departamento
mesas_departamento=pd.DataFrame(dataframe.groupby("DEPARTAMENTO").size(),
columns=['mesas_votacion'])
mesas_departamento.reset_index(inplace=True)
mesas_departamento.sample(5)
| DEPARTAMENTO | mesas_votacion | |
|---|---|---|
| 24 | UCAYALI | 1310 |
| 7 | CUSCO | 3523 |
| 11 | JUNIN | 3421 |
| 9 | HUANUCO | 2047 |
| 3 | AREQUIPA | 3950 |
import plotly.express as px
fig=px.line(mesas_departamento, x="DEPARTAMENTO",
y="mesas_votacion")
fig.show()
values_3=dataframe.iloc[:, [5,9,10]] # seleccion columnas a analizar
values_3
| DEPARTAMENTO | PERU_LIBRE | FUERZA_POPULAR | |
|---|---|---|---|
| 0 | AMAZONAS | 118 | 81 |
| 1 | AMAZONAS | 123 | 87 |
| 2 | AMAZONAS | 122 | 73 |
| 3 | AMAZONAS | 131 | 89 |
| 4 | AMAZONAS | 107 | 95 |
| ... | ... | ... | ... |
| 82725 | UCAYALI | 43 | 76 |
| 82726 | UCAYALI | 28 | 73 |
| 82727 | UCAYALI | 44 | 81 |
| 82728 | UCAYALI | 32 | 104 |
| 82729 | UCAYALI | 50 | 77 |
82730 rows × 3 columns
values_3.describe()
| PERU_LIBRE | FUERZA_POPULAR | |
|---|---|---|
| count | 82730.000000 | 82730.000000 |
| mean | 105.308751 | 103.338994 |
| std | 45.444055 | 51.254139 |
| min | 0.000000 | 0.000000 |
| 25% | 73.000000 | 61.000000 |
| 50% | 98.000000 | 111.000000 |
| 75% | 130.000000 | 144.000000 |
| max | 269.000000 | 271.000000 |
Se procede a la visualizacion de los datos para analizar el comportamiento y distribución, para ello, tenemos librerías fundamentales para poder trabajarlo: MatPlotlib ,Plotly y Seaborn. Veamos algunas conclusiones sobre la distribución de nuestros datos.
plt.figure(1)
plt.subplots(figsize=(20,20))
plt.subplot(421)
sns.distplot(values_3['PERU_LIBRE'])
plt.title('HIST/DISTRIBUCION PL')
plt.grid(True)
plt.subplot(422)
sns.distplot(values_3['FUERZA_POPULAR'])
plt.title('HIST/DISTRIBUCION FP')
plt.grid(True)
plt.show()
<Figure size 432x288 with 0 Axes>
fig = plt.figure(figsize = (30,20))
ax = fig.gca()
values_3.plot( ax=ax , kind= 'density', subplots= True, layout = (5,4), sharex= False)
plt.show()
C:\Users\firebaseapk\anaconda3\lib\site-packages\pandas\plotting\_matplotlib\__init__.py:61: UserWarning: To output multiple subplots, the figure containing the passed axes is being cleared
sns.relplot(x="PERU_LIBRE", y="FUERZA_POPULAR",
hue="DEPARTAMENTO",
sizes=(15, 200),
data=values_3).set(title='Dispersion de votos en Mesas de sufragio PL-FP\n Fig3');
plt.gcf().set_size_inches(20, 10)
values_3.skew()
PERU_LIBRE 0.676460 FUERZA_POPULAR -0.196937 dtype: float64
se observa en el boxplot
pero existen datos dispersos superiores a la media, estos datos inicializan a partir de 200 , muy alejados y aglomerados entre ese rango , esto es una prueba clara de que existen outlayers en los datos y a su vez se encuentran valores muy dispersos que nose ajustarian a una distribucion normal ,estos valores atípicos están mas allá del límite superior.
los outlayers visualizados son sustento base para inferir que hubo manipulacion para generar esos valores anomaloes
import plotly.graph_objects as go
y0 = values_3['PERU_LIBRE']
y1 = values_3['FUERZA_POPULAR']
fig = go.Figure()
fig.add_trace(go.Box(y=y0, name="Peru Libre"))
fig.update_layout(title="boxplot/observation outlayers ", xaxis_title="Partidos",yaxis_title="Numero de Votos", legend_title="Partidos", width=600,
height=800)
fig.add_trace(go.Box(y=y1, name="Fuerza Popular"))
fig.show()
isolation Forest es una método no supervisado para identificar anomalías (outliers) cuando los datos no están etiquetados, es decir, no se conoce la clasificación real (anomalía - no anomalía) de las observaciones.
Su funcionamiento está inspirado en el algoritmo de clasificación y regresión Random Forest. Al igual que en Random Forest, un modelo Isolation Forest está formado por la combinación de múltiples árboles llamados isolation trees. Estos árboles se crea de forma similar a los de clasificación-regresión: las observaciones de entrenamiento se van separando de forma recursiva creando las ramas del árbol hasta que cada observación queda aislada en un nodo terminal. Sin embargo, en los isolation tree, la selección de los puntos de división se hace de forma aleatoria. Aquellas observaciones con características distintas al resto, quedarán aisladas a las pocas divisiones, por lo que el número de nodos necesarios para llegar a estas observación desde el inicio del árbol (profundidad) es menor que para el resto.
Las anomalías son patrones de datos que tienen características de datos diferentes a las de las instancias normales. La detección de anomalías tiene una relevancia significativa y, a menudo, proporciona información procesable crítica en varios dominios de aplicaciones
from sklearn.ensemble import IsolationForest
isolation_forest = IsolationForest(n_estimators=100)
isolation_forest.fit(values_3['PERU_LIBRE'].values.reshape(-1, 1))
xx = np.linspace(values_3['PERU_LIBRE'].mean(),
values_3['PERU_LIBRE'].max(), len(values_3)).reshape(-1,1)
anomaly_score = isolation_forest.decision_function(xx)
outlier = isolation_forest.predict(xx)
plt.figure(figsize=(10,4))
plt.plot(xx, anomaly_score, label='anomaly score')
plt.fill_between(xx.T[0], np.min(anomaly_score), np.max(anomaly_score),
where=outlier==-1, color='r',
alpha=.4, label='outlier region')
plt.legend()
plt.ylabel('anomaly score')
plt.xlabel('Votos anomalos')
plt.show();
peru_libre=pd.DataFrame(values_3['PERU_LIBRE'].values>150 ,
columns=['MAYOR_150'])
peru_libre.sample(100)
| MAYOR_150 | |
|---|---|
| 33593 | False |
| 59401 | False |
| 9073 | False |
| 74727 | True |
| 42165 | False |
| ... | ... |
| 4545 | True |
| 57381 | False |
| 22200 | False |
| 40637 | False |
| 32607 | False |
100 rows × 1 columns
fig =plt.figure(figsize=(8,12))
sns.countplot(peru_libre["MAYOR_150"])
<AxesSubplot:xlabel='MAYOR_150', ylabel='count'>
peru_libre.groupby("MAYOR_150").size()
MAYOR_150 False 68864 True 13866 dtype: int64
valores observados 13866 , valores con anomalias
values_4=dataframe.iloc[:, [0,5,9,10]] # 13866
| mesa | DEPARTAMENTO | PERU_LIBRE | FUERZA_POPULAR | |
|---|---|---|---|---|
| 0 | 1 | AMAZONAS | 118 | 81 |
| 1 | 2 | AMAZONAS | 123 | 87 |
| 2 | 3 | AMAZONAS | 122 | 73 |
| 3 | 4 | AMAZONAS | 131 | 89 |
peru_libre_outlayers=pd.DataFrame(values_4['PERU_LIBRE'].values>200 ,
columns=['MAYOR_150'])
peru_libre_outlayers.sample(100)
| MAYOR_200 | |
|---|---|
| 47315 | False |
| 23148 | False |
| 59620 | False |
| 70069 | True |
| 58658 | False |
| ... | ... |
| 32416 | False |
| 57031 | False |
| 68003 | False |
| 18838 | False |
| 80830 | False |
100 rows × 1 columns
dataframe.iloc[70069]
mesa 70358 CCODI_UBIGEO 200401 TNOMB_LOCAL IEP 71003 REGION SUR REGION_NATURAL Sierra DEPARTAMENTO PUNO PROVINCIA CHUCUITO DISTRITO JULI NNUME_HABILM 300 PERU_LIBRE 229 FUERZA_POPULAR 18 VALIDOS 247 BLANCO 1 NULOS 4 IMPUGNADOS 0 EMITIDOS 252 NULO_VICIADO 5 MESAS_LOCAL 12 MESAS_DISTR 59 Name: 70069, dtype: object
mesa_70069=pd.DataFrame(dataframe.iloc[70069])
mesa_70069
| 70069 | |
|---|---|
| mesa | 70358 |
| CCODI_UBIGEO | 200401 |
| TNOMB_LOCAL | IEP 71003 |
| REGION | SUR |
| REGION_NATURAL | Sierra |
| DEPARTAMENTO | PUNO |
| PROVINCIA | CHUCUITO |
| DISTRITO | JULI |
| NNUME_HABILM | 300 |
| PERU_LIBRE | 229 |
| FUERZA_POPULAR | 18 |
| VALIDOS | 247 |
| BLANCO | 1 |
| NULOS | 4 |
| IMPUGNADOS | 0 |
| EMITIDOS | 252 |
| NULO_VICIADO | 5 |
| MESAS_LOCAL | 12 |
| MESAS_DISTR | 59 |
print("Url ONPE Acta mesa\n #n 70358 ")
Url ONPE Acta mesa #n 70358
Tomanos una muestra donde se encuentran la mesa observada con anomalias y los agrupamos segun el departamento donde pertenece
values_5=dataframe[['DEPARTAMENTO','PERU_LIBRE']]
values_5.shape
(82730, 2)
outlayers_puno=values_5[(values_5.DEPARTAMENTO=='PUNO')&
(values_5.PERU_LIBRE>150)]
outlayers_puno.describe()
| PERU_LIBRE | |
|---|---|
| count | 3069.000000 |
| mean | 206.845226 |
| std | 21.864325 |
| min | 151.000000 |
| 25% | 192.000000 |
| 50% | 206.000000 |
| 75% | 222.000000 |
| max | 269.000000 |
plt.figure(1)
plt.subplots(figsize=(20,20))
plt.subplot(421)
sns.distplot(outlayers_puno['PERU_LIBRE'])
plt.title('Distribucion PL')
plt.grid(True)
<Figure size 432x288 with 0 Axes>
se observa que el histograma y la distribucion presenta una distribucion casi normal
fig=plt.figure(figsize=(10,10))
ax=fig.gca()
outlayers_puno.plot(ax=ax,kind='box',subplots=True,layout=(6,5),sharex=False)
plt.show()
plt.scatter(range(values_3.shape[0]),
np.sort(values_3['PERU_LIBRE'].values))
plt.xlabel('Total votos ')
plt.ylabel('votos a favor X mesa')
plt.title(" Tendencia de votos x mesa PL")
sns.despine()
existe una clara tendencia creciente de votos a favor de PL , en el grafico se muestra que no existe un intervalo que haga un corte a la tendencia
indica el número de estándar las desviaciones de una observación o dato está por encima o por debajo de la media
z score mide la desviacion de una observacion con respecto al universo
dataframe_z=dataframe.iloc[:, [5,9]]
values_z=pd.DataFrame(dataframe_z.groupby("DEPARTAMENTO").sum())# sumatoria de votos en mesa
values_z.reset_index()
| DEPARTAMENTO | PERU_LIBRE | |
|---|---|---|
| 0 | AMAZONAS | 121162 |
| 1 | ANCASH | 347457 |
| 2 | APURIMAC | 173835 |
| 3 | AREQUIPA | 558085 |
| 4 | AYACUCHO | 266652 |
| 5 | CAJAMARCA | 509638 |
| 6 | CALLAO | 198503 |
| 7 | CUSCO | 610353 |
| 8 | HUANCAVELICA | 166044 |
| 9 | HUANUCO | 255391 |
| 10 | ICA | 233316 |
| 11 | JUNIN | 396598 |
| 12 | LA LIBERTAD | 392066 |
| 13 | LAMBAYEQUE | 289784 |
| 14 | LIMA | 2187228 |
| 15 | LORETO | 193765 |
| 16 | MADRE DE DIOS | 57387 |
| 17 | MOQUEGUA | 80520 |
| 18 | PASCO | 82851 |
| 19 | PIURA | 388093 |
| 20 | PUNO | 645813 |
| 21 | SAN MARTIN | 241317 |
| 22 | TACNA | 154223 |
| 23 | TUMBES | 41464 |
| 24 | UCAYALI | 120648 |
values_z.describe()
| PERU_LIBRE | |
|---|---|
| count | 2.500000e+01 |
| mean | 3.484877e+05 |
| std | 4.198268e+05 |
| min | 4.146400e+04 |
| 25% | 1.542230e+05 |
| 50% | 2.413170e+05 |
| 75% | 3.920660e+05 |
| max | 2.187228e+06 |
values_z['zScore'] = (values_z["PERU_LIBRE"] - values_z["PERU_LIBRE"].mean() ) / values_z["PERU_LIBRE"].std()
values_z
| PERU_LIBRE | zScore | |
|---|---|---|
| DEPARTAMENTO | ||
| AMAZONAS | 121162 | -0.541475 |
| ANCASH | 347457 | -0.002455 |
| APURIMAC | 173835 | -0.416011 |
| AREQUIPA | 558085 | 0.499247 |
| AYACUCHO | 266652 | -0.194927 |
| CAJAMARCA | 509638 | 0.383849 |
| CALLAO | 198503 | -0.357254 |
| CUSCO | 610353 | 0.623746 |
| HUANCAVELICA | 166044 | -0.434569 |
| HUANUCO | 255391 | -0.221750 |
| ICA | 233316 | -0.274332 |
| JUNIN | 396598 | 0.114596 |
| LA LIBERTAD | 392066 | 0.103801 |
| LAMBAYEQUE | 289784 | -0.139828 |
| LIMA | 2187228 | 4.379759 |
| LORETO | 193765 | -0.368539 |
| MADRE DE DIOS | 57387 | -0.693383 |
| MOQUEGUA | 80520 | -0.638282 |
| PASCO | 82851 | -0.632729 |
| PIURA | 388093 | 0.094337 |
| PUNO | 645813 | 0.708209 |
| SAN MARTIN | 241317 | -0.255274 |
| TACNA | 154223 | -0.462726 |
| TUMBES | 41464 | -0.731310 |
| UCAYALI | 120648 | -0.542699 |
values_pos=values_z[(values_z.zScore>0)]
values_pos=values_pos['zScore']
values_pos
DEPARTAMENTO AREQUIPA 0.499247 CAJAMARCA 0.383849 CUSCO 0.623746 JUNIN 0.114596 LA LIBERTAD 0.103801 LIMA 4.379759 PIURA 0.094337 PUNO 0.708209 Name: zScore, dtype: float64
values_pos[(values_pos>=3.5)] # dato atipico
DEPARTAMENTO LIMA 4.379759 Name: zScore, dtype: float64
values_neg=values_z[(values_z.zScore<0)]
values_neg=values_neg['zScore']
values_neg
DEPARTAMENTO AMAZONAS -0.541475 ANCASH -0.002455 APURIMAC -0.416011 AYACUCHO -0.194927 CALLAO -0.357254 HUANCAVELICA -0.434569 HUANUCO -0.221750 ICA -0.274332 LAMBAYEQUE -0.139828 LORETO -0.368539 MADRE DE DIOS -0.693383 MOQUEGUA -0.638282 PASCO -0.632729 SAN MARTIN -0.255274 TACNA -0.462726 TUMBES -0.731310 UCAYALI -0.542699 Name: zScore, dtype: float64
import plotly.graph_objects as go
deps = values_z.index.tolist()
data=values_z['zScore']
fig = go.Figure()
fig.add_trace(go.Bar(x=deps, y=data,
base=[-0.7],
marker_color='crimson',
name='expenses'))
fig.show()
En el analisis del universo los outlayers en las mesas de votacion con votos mayores a 200 a favor de PL son muy marcadas y tambien se observan datos dispersos (fig3) , esto podria ser prueba de que existio una manipulacion externa para la generacion de esos valores y favorecer a PL.
Pero analizando las muestras que generaron esos outlayers se observa que una de las muestras tomadas aleatoriamente sigue una tendencia creciente superior a la media del universo con una distribucion normal y poca dispersion que favorecieron a PL en cuanto a votos.
De lo contratio si estas mesas que generaron outlayers no tendrian una tendecia en cuanto votos y estarian muy dispersas seria indicio a discutir de que si existio manipulacion a favor de PL
Las puntuaciones del z-score no mostraron valores suficientemente altos para concluir a excepsion de Lima.
</HTML>